package sdk

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	urllib "net/url"
	"reflect"
	"regexp"
	"strings"

	"gitee.com/youkelike/ziam/zsdk/sdk/log"
	"gitee.com/youkelike/ziam/zsdk/sdk/request"
)

// 这个文件用于构造请求参数，包括 url 中的 path、query 和 body

// url 中不需要转码的字符
var noEscape [256]bool
var baseRequestFields []string

func init() {
	req := request.BaseRequest{}
	reqType := reflect.TypeOf(req)
	for i := 0; i < reqType.NumField(); i++ {
		baseRequestFields = append(baseRequestFields, reqType.Field(i).Name)
	}

	for i := 0; i < len(noEscape); i++ {
		// expects every character except these to be escaped
		noEscape[i] = (i >= 'A' && i <= 'Z') ||
			(i >= 'a' && i <= 'z') ||
			(i >= '0' && i <= '9') ||
			i == '-' ||
			i == '.' ||
			i == '_' ||
			i == '~'
	}
}

type ParameterBuilder interface {
	BuildURL(url string, paramJson []byte) (string, error)
	BuildBody(paramJson []byte) (string, error)
}

func GetParameterBuilder(method string, logger log.Logger) ParameterBuilder {
	if method == "GET" || method == "DELETE" || method == "HEAD" {
		return &WithoutBodyBuilder{logger}
	} else {
		return &WithBodyBuilder{logger}
	}
}

type WithBodyBuilder struct {
	Logger log.Logger
}

func (b WithBodyBuilder) BuildURL(url string, paramJson []byte) (string, error) {
	paramMap := make(map[string]interface{})
	err := json.Unmarshal(paramJson, &paramMap)
	if err != nil {
		b.Logger.Errorf("%s", err.Error())
		return "", err
	}

	// 替换 path 中的 {XXX} 格式的参数
	replacedUrl, err := replaceUrlWithParam(url, paramMap)
	if err != nil {
		b.Logger.Errorf("%s", err.Error())
		return "", err
	}

	// 转义 url 中的特殊字符
	encodedUrl, err := encodeUrl(replacedUrl, nil)
	if err != nil {
		return "", err
	}

	b.Logger.Infof("URL=%s", encodedUrl)
	return encodedUrl, nil
}

func (b WithBodyBuilder) BuildBody(paramJson []byte) (string, error) {
	paramMap := make(map[string]interface{})
	err := json.Unmarshal(paramJson, &paramMap)
	if err != nil {
		b.Logger.Errorf("%s", err.Error())
		return "", err
	}

	// 去掉定义在 BaseRequest 中的字段，但已在 url 中挖坑的没排除
	for k := range paramMap {
		if includes(baseRequestFields, k) {
			delete(paramMap, k)
		}
	}

	body, _ := json.Marshal(paramMap)
	b.Logger.Infof("Body=%s", string(body))
	return string(body), nil
}

type WithoutBodyBuilder struct {
	Logger log.Logger
}

func (b WithoutBodyBuilder) BuildURL(url string, paramJson []byte) (string, error) {
	paramMap := make(map[string]interface{})
	err := json.Unmarshal(paramJson, &paramMap)
	if err != nil {
		b.Logger.Errorf("%s", err.Error())
		return "", err
	}

	// 替换 path 中的 {XXX} 格式的参数
	resultUrl, err := replaceUrlWithParam(url, paramMap)
	if err != nil {
		b.Logger.Errorf("%s", err.Error())
		return "", err
	}
	// 构造 url 中的 query 部分
	queryParams := buildQueryParams(paramMap, url)
	// 转义 url 中的特殊字符
	encodedUrl, err := encodeUrl(resultUrl, queryParams)
	if err != nil {
		return "", err
	}

	b.Logger.Infof("%s", string(paramJson))
	b.Logger.Infof("URL=%s", encodedUrl)
	return encodedUrl, nil
}

func (b WithoutBodyBuilder) BuildBody(paramJson []byte) (string, error) {
	return "", nil
}

func replaceUrlWithParam(url string, paramMap map[string]interface{}) (string, error) {
	r, _ := regexp.Compile("{[a-zA-Z0-9-_]+}")
	matches := r.FindAllString(url, -1)
	for _, match := range matches {
		field := strings.TrimLeft(match, "{")
		field = strings.TrimRight(field, "}")
		value, ok := paramMap[field]
		if !ok {
			return "", errors.New("Can not find path parameter: " + field)
		}

		valueStr := fmt.Sprintf("%v", value)
		url = strings.Replace(url, match, valueStr, -1)
	}

	return url, nil
}

func buildQueryParams(paramMap map[string]interface{}, url string) urllib.Values {
	values := urllib.Values{}
	accessMap(paramMap, url, "", values)
	return values
}

func accessMap(paramMap map[string]interface{}, url string, prefix string, values urllib.Values) {
	for k, v := range paramMap {
		// 去掉已在 url 中挖坑的和定义在 BaseRequest 中的字段
		if shouldIgnoreField(url, k) {
			continue
		}

		switch e := v.(type) {
		case []interface{}:
			for i, n := range e {
				switch f := n.(type) {
				case map[string]interface{}:
					prefix = fmt.Sprintf("%s.%d", k, i+1)
					accessMap(f, url, prefix, values)
				case nil:
				default:
					values.Set(fmt.Sprintf("%s%s.%d", prefix, k, i+1), fmt.Sprintf("%v", n))
				}
			}
		case nil:
		default:
			values.Set(fmt.Sprintf("%s%s", prefix, k), fmt.Sprintf("%v", v))
		}
	}
}

func shouldIgnoreField(url, field string) bool {
	flag := "{" + field + "}"
	if strings.Contains(url, flag) {
		return true
	}

	if includes(baseRequestFields, flag) {
		return true
	}

	return false
}

func encodeUrl(url string, values urllib.Values) (string, error) {
	urlObj, err := urllib.Parse(url)
	if err != nil {
		return "", err
	}

	// 手动对 Path 中的特殊字符进行转义
	urlObj.RawPath = EscapePath(urlObj.Path, false)
	uri := urlObj.EscapedPath()
	// 在 Path 后面拼接 Query 串
	if values != nil {
		queryParam := values.Encode()
		queryParam = strings.Replace(queryParam, "+", "%20", -1)
		if queryParam != "" {
			uri += "?" + queryParam
		}
	}

	return uri, nil
}

// 把 url 中的特殊字符转成 16 进制编码
func EscapePath(path string, encodeSep bool) string {
	var buf bytes.Buffer
	for i := 0; i < len(path); i++ {
		c := path[i]
		if noEscape[c] || (c == '/' && !encodeSep) {
			buf.WriteByte(c)
		} else {
			// 前两个 % 标识输出一个 %，第三个百分号才用于标识输出格式，
			// %02X 表示输出 2 位数字的十六进制，不足 2 位在前面补上 0
			fmt.Fprintf(&buf, "%%%02X", c)
		}
	}
	return buf.String()
}
